package org.utils.xml;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * This class represent a tree storing XML data kinds,
 * i.e. : an element name, a value, a hashtable of attributes and a ArrayList of child<p>
 * important : THIS CLASS IS NOT SYNCHRONIZED !
 *
 * @version 1.0
 */

public class XMLDataNode implements Cloneable {
	protected static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

	protected static final String SPACE = " ";

	protected static final String OPENING_ANGLE_START_TAG = "<";

	protected static final String OPENING_ANGLE_END_TAG = "</";

	protected static final String CLOSING_ANGLE = ">";

	protected static final String NEWLINE = "\n";

	protected static final int NO_ATTRIBUTES = 0;

	protected String mName = null;

	protected String mValue = null;

	protected Hashtable mAttributes = new Hashtable();

	protected XMLDataNode mFather = null;

	protected ArrayList mChildren = new ArrayList();

	/*Hashtable*/

	/** Simple Constructor */
	public XMLDataNode(String pName) {
		mName = new String(pName);
	}

	/** Constructor with set of father */
	public XMLDataNode(String pName, XMLDataNode pNodeFather) {
		mName = new String(pName);
		// pNodeFather will be null for the root.
		mFather = pNodeFather;
	}

	/**
	 * Perform a full deep copy (clone) a the tree.<br>
	 * <p>Here is a sample code:
	 * <code>
	 * XMLDataNode clonedMainObjData = (XMLDataNode) mainObjectResultsTree.clone();<br>
	 * </code>
	 * the cast is required because of java's <code>Object</code> interface.</p>
	 */
	public Object clone() {
		XMLDataNode clone = new XMLDataNode(mName, mFather);

		// Full DEEP clone of attributes
		Iterator itAttributes = mAttributes.keySet().iterator();
		while (itAttributes.hasNext()) {
			String attributeName = (String) itAttributes.next();
			clone.addAttribute(new String(attributeName), new String(
					(String) mAttributes.get(attributeName)));
		}

		// Full DEEP clone of (Full DEEP cloned) children XML node (recursively)
		for (int i = 0; i < mChildren.size(); i++) {
			XMLDataNode currentChild = (XMLDataNode) mChildren.get(i);
			clone.addChild((XMLDataNode) currentChild.clone());
		}

		return clone;
	}

	/**
	 * Returns node's father
	 */
	public XMLDataNode getFather() {
		return mFather;
	}

	/**
	 * Update the "father" link of a node.<br>
	 * <B>Don't forget to do it if your changing the structure of a tree !</B>
	 *
	 * Here is a sample code:<p>
	 * <code>
	 * tree = (XMLDataNode) resultingTree.getChildren().get(0);<br>
	 * tree.setFather(null);<br>
	 * </code><p>
	 */
	public void setFather(XMLDataNode pFather) {
		mFather = pFather;
	}

	/**
	 * Retrieve a child node with his name.<br>
	 * <B>returns only the first one found !!!</B>
	 *
	 * Here is a sample code:<p>
	 * <code>
	 * try {<br>
	 * &nbsp;&nbsp;&nbsp;String folderValue = (String) resultingTree.getChild("Yadis").getChild("log").getChild("output").getAttributes().get("folder");<br>
	 * &nbsp;&nbsp;&nbsp;if(folderValue == null)<br>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new java.lang.NullPointerException();<br>
	 * &nbsp;&nbsp;&nbsp;	System.out.println("folder = "+folderValue);<br>
	 * } catch(java.lang.NullPointerException e) {<br>
	 * &nbsp;&nbsp;&nbsp;	System.err.println("attr introuvable ... :-(");<br>
	 * }<br>
	 * </code><p>
	 *
	 * WARNING : this method only returned the FIRST child found !
	 * if a node have some children with the same name, you will have
	 * to iterate on children with getChildren().
	 */
	public XMLDataNode getChild(String pChildName) {
		for (int i = 0; i < mChildren.size(); i++) {
			if (((XMLDataNode) mChildren.get(i)).getName().equalsIgnoreCase(
					pChildName))
				return (XMLDataNode) mChildren.get(i);
		}
		return null;
	}

	/** Returns node's name */
	public String getName() {
		return mName;
	}

	/** Set node's name */
	public void setName(String pNewName) {
		mName = pNewName;
	}

	/** Get node's value */
	public String getValue() {
		return mValue; //(String) mAttributes.get("value");
	}

	/** Set node's value */
	public void setValue(String pValue) {
		mValue = pValue; //mAttributes.put("value", value);
	}

	/** Get node's attributes */
	public Hashtable getAttributes() {
		return mAttributes;
	}

	/** Get node's attribute by name */
	public String getAttribute(String name) {
		return (String) mAttributes.get(name);
	}

	/** Get node's childs */
	public ArrayList getChildren() {
		return mChildren;
	}

	/** Get node's childs */
	public boolean hasChildren() {
		return (mChildren.size() != 0);
	}

	/** Get node's childs iterator*/
	public Iterator getChildrenIterator() {
		return mChildren.iterator();
	}

	/** Get node's childs iterator*/
	public Object[] getChildrenArray() {
		XMLDataNode[] children = new XMLDataNode[mChildren.size()];
		return mChildren.toArray(children);
	}

	/**
	 * This method is used to determinate the sequence order of an element between his brother
	 * having the same name
	 */
	public int calculateSequenceOrder() {
		XMLDataNode father = getFather();
		String nodeName = getName();
		int currentSequenceOrder = 0;
		int sequenceOrder = 0;
		boolean partOfSequence = false;

		if (father != null) {
			for (int i = 0; i < father.getChildren().size(); i++) {
				Object fatherChild = father.getChildren().get(i);
				// Same name as current node ?
				if (((XMLDataNode) fatherChild).getName().equals(nodeName)) {
					if (fatherChild == (Object) this)
						sequenceOrder = currentSequenceOrder;
					else
						partOfSequence = true;
					currentSequenceOrder++;
				}
			}
		}

		if (partOfSequence == true)
			return sequenceOrder;
		else
			return Integer.MAX_VALUE;
	}

	/**
	 * Returns the node's depth to the tree root
	 */
	public int depth() {
		XMLDataNode currentNode = this;
		int depthLevel = 0;

		while (currentNode.getFather() != null) {
			depthLevel++;
			currentNode = (XMLDataNode) currentNode.getFather();
		}
		return depthLevel;
	}

	/** Change the value of an attribute */
	public void setAttribute(String pKey, String pValue) {
		addAttribute(pKey, pValue);
	}

	/** Add an attribute to the node */
	public void addAttribute(String pKey, String pValue) {
		mAttributes.put(pKey, pValue);
	}

	/**
	 * Add a child to the node
	 * This method updates the "father" link
	 */
	public void newChild(String pChildNodeName) {
		addChild(new XMLDataNode(pChildNodeName));
	}

	/**
	 * Add a child to the node
	 * This method updates the "father" link
	 */
	public void addChild(XMLDataNode pChildNode) {
		mChildren.add(pChildNode);
		pChildNode.setFather(this);
	}

	/**
	 * Add a child to the node
	 * This method updates the "father" link
	 */
	public void addChildren(ArrayList pChildren) {
		for (int i = 0; i < pChildren.size(); i++) {
			addChild((XMLDataNode) pChildren.get(i));
		}
	}

	/**
	 * rename (recursively) an attribute name in all the tree.<br>
	 * @param previousValue attribute name to change
	 * @param newValue the value to set to found attributes
	 */
	public void renameAttribute(String previousValue, String newValue) {
		// Rename attribute if found
		//	String associatedValue = (String) mAttributes.get(previousValue);
		//	if (associatedValue != null) {
		if (mAttributes.containsKey(previousValue)) {
			mAttributes.put(newValue, mAttributes.get(previousValue));
			mAttributes.remove(previousValue);
		}
		// Broadcast message to children
		for (int i = 0; i < mChildren.size(); i++)
			((XMLDataNode) mChildren.get(i)).renameAttribute(previousValue,
					newValue);
	}

	/**
	 * Indentation increment. Define the length of the space added before the line depending on the depth of the current node
	 */
	protected final static int indentationLevel = 3;

	/**
	 * Generate an XML String with the content of the tree.
	 */
	public String toXMLString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(XML_HEADER).append(NEWLINE);
		toXMLString(buffer);
		return buffer.toString();
	}

	/**
	 * Generate an XML String with the content of the tree.<br>
	 * Here is a sample code:<p>
	 * <code>
	 * StringBuffer xmlResult = new StringBuffer(200);<br>
	 * resultNode = new XMLDataNode(mBusinessClass, null);<br>
	 * ... <br>
	 * resultNode.toXMLString(xmlResult);<br>
	 * </code><p>
	 */
	public void toXMLString(StringBuffer xmlBuffer) {
		if (xmlBuffer == null)
			throw new java.lang.NullPointerException();

		addIndentationSpaces(xmlBuffer, depth() * indentationLevel);

		openElement(xmlBuffer, mName);

		// Process attributes
		if (mAttributes.size() == NO_ATTRIBUTES) {
			// no attibutes
			closeOpenedElement(xmlBuffer);
		} else {
			// several attributes
			Iterator itAttributes = mAttributes.keySet().iterator();
			while (itAttributes.hasNext()) {
				String attributeName = (String) itAttributes.next();
				xmlBuffer.append(SPACE);
				xmlBuffer.append(attributeName);
				xmlBuffer.append("=\"");
				xmlBuffer.append((String) mAttributes.get(attributeName));
				xmlBuffer.append("\"");
			}
			closeOpenedElement(xmlBuffer);
		}

		if (mChildren.size() == 0) {
			if (mValue != null)
				xmlBuffer.append(mValue);
		} else {
			// process children
			xmlBuffer.append(NEWLINE);
			for (int i = 0; i < mChildren.size(); i++)
				((XMLDataNode) mChildren.get(i)).toXMLString(xmlBuffer);
			addIndentationSpaces(xmlBuffer, depth() * indentationLevel);
		}
		// append "closing" element
		closeElement(xmlBuffer, mName);
		xmlBuffer.append(NEWLINE);
	}

	/** Make a call to the method <code>toXMLString</code> */
	public String toString() {
		return getName();
	}

	protected void addIndentationSpaces(StringBuffer buf, int indentation) {
		for (int i = 0; i < indentation; i++)
			buf.append(SPACE);
	}

	protected void openElement(StringBuffer buf, String elementName) {
		buf.append(OPENING_ANGLE_START_TAG);
		buf.append(elementName);
	}

	protected void closeOpenedElement(StringBuffer buf) {
		buf.append(CLOSING_ANGLE);
	}

	protected void closeElement(StringBuffer buf, String elementName) {
		buf.append(OPENING_ANGLE_END_TAG);
		buf.append(elementName);
		buf.append(CLOSING_ANGLE);
	}
}
